package com.tangxz.chat.modules.netty;

/**
 * @author: 唐小尊
 * @email: 1171702529@qq.com
 * @cate: 2021/04/17 16:38
 */

import com.alibaba.fastjson.JSON;
import com.tangxz.chat.common.cache.ChannelCaffeineCache;
import com.tangxz.chat.common.cache.MsgGroupMongoCache;
import com.tangxz.chat.common.cache.UserRedisCache;
import com.tangxz.chat.common.dto.Msg;
import com.tangxz.chat.common.dto.MsgContentDto;
import com.tangxz.chat.common.dto.MsgGroupDto;
import com.tangxz.chat.common.dto.UserRedisDto;
import com.tangxz.chat.common.enums.MsgAction;
import com.tangxz.chat.common.enums.MsgType;
import com.tangxz.chat.common.enums.UserState;
import com.tangxz.chat.config.ApplicationContextProvider;
import com.tangxz.chat.modules.user.entity.TUser;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.List;


/**
 * 用于处理消息的handler
 * 由于它的传输数据的载体是frame，这个frame在netty中，
 * 是用于为websocket专门处理文本对象的，
 * frame是消息的载体，次类叫：TextWebSocketFrame
 */
@Component
@Slf4j
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    //使用@Resource注入需要使用的bean，包括redis
    UserRedisCache redisService = ApplicationContextProvider.getBean(UserRedisCache.class);
    MsgGroupMongoCache mongoDbService = ApplicationContextProvider.getBean(MsgGroupMongoCache.class);

    //用于记录和管理所有客户端的管道 channel
    private static ChannelGroup users = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    //服务器接收到消息
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        //获取客户端所传输的消息
        String content = msg.text();
        System.out.println("接收到的数据：" + content);
        //1. 获取客户端发来的消息
//        JsonUtils.jsonToPojo()
        MsgContentDto dataContent = JSON.parseObject(content, MsgContentDto.class);
        Integer action = dataContent.getAction();
        Channel channel = ctx.channel();
        if (action.equals(MsgAction.CHAT.type)) {
            System.out.println(action);
        }
        //2. 判断消息的类型，根据不同的类型来处理不同的业务
        if (action.equals(MsgAction.CONNECT.type)) {
            System.out.println("当websocket 第一次open的时候，初始化channel");
            //2.1 当websocket 第一次open的时候，初始化channel，把用的channel 和 userid 关联起来，
            UserConnectMsgHandle(dataContent, channel);
        } else if (action.equals(MsgAction.CHAT.type)) {
            //2.2 聊天类型的消息，把聊天记录保存到数据库中，同时标记消息的签收状态[未签收]
            UserChatMsgHandle(dataContent, channel);
            //给发送者一个可靠反馈
        }
    }


    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
//        System.out.println("建立连接 - 添加");
        users.add(ctx.channel());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        //关闭之后，自动移除，所以没必要写下面这段代码
        //users.remove(ctx.channel());
        //TODO redis存储离线用户
        System.out.println("客户端断开，channel对应的长Id为：" + ctx.channel().id().asLongText());
        System.out.println("客户端断开，channel对应的短Id为：" + ctx.channel().id().asShortText());

        //TODO 客户端断开，要记录断开时间，下线时间。
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        //TODO 发生了异常后关闭连接，同时从channelgroup移除
        //TODO redis存储离线用户
        ctx.channel().close();
        users.remove(ctx.channel());

    }

    /**
     * 1 用户连接请求处理
     *
     * @param dataContent
     * @param channel
     */
    private void UserConnectMsgHandle(MsgContentDto dataContent, Channel channel) {
        //得到发送连接请求的用户id
        String senderId = dataContent.getSenderId();
        String extend = dataContent.getExtend();
        TUser tUser = JSON.parseObject(extend, TUser.class);
        //在redis中，保存用户channelId，设置为在线，保存当前时间
        users.add(channel);
        //登录用户的channel存入本地缓存
        ChannelCaffeineCache.put(dataContent.getReceiverId(), channel);
        //登录用户存入redis
        redisService.save(senderId, new UserRedisDto(tUser, UserState.ONLINE.type, dataContent.getMsg().getTime()));
        //从mongoDb中取出消息
        List<MsgGroupDto> oneUserMsgGroupDtos = mongoDbService.getOneUserMsgGroups("m" + senderId);
        // 如果消息缓存不为空，则推送，否则不发任何内容
        if (oneUserMsgGroupDtos.size() != 0) {
            sendMsg(channel, oneUserMsgGroupDtos);
        }
    }

    /**
     * 2 用户聊天消息
     *
     * @param dataContent
     */
    private void UserChatMsgHandle(MsgContentDto dataContent, Channel channel) {
        Msg chatMsg = dataContent.getMsg();
        String senderId = dataContent.getSenderId();
        Long time = chatMsg.getTime();
        String receiverId = dataContent.getReceiverId();
        //保存消息到数据库，并且标记为未签收
        //TODO 保存消息到数据库，并且标记为未签收
        //发送消息
        Channel receiverChannel = ChannelCaffeineCache.get(receiverId);
        if (receiverChannel == null) {
            // 缓存中没有这个用户的通道，用户离线、用户下线。
            //1. 格式化数据
            Msg msgPo = new Msg(chatMsg.getContent(), chatMsg.getType(), chatMsg.getTime(), false);
            //2. 存储数据
            mongoDbService.saveMsg(receiverId, senderId, msgPo);
            System.out.println("发送消息给客户端");
            MsgContentDto msgContentDto = new MsgContentDto(MsgAction.SIGNED.type, senderId, receiverId, new Msg("" + msgPo.getTime(), MsgType.RECEIVE.type, time, false), null);
            sendMsg(channel, msgContentDto);
        } else {
            //缓存中找到这个用户的通道
            //当receiverChannel不为空的时候，从ChannelGroup 去查找对应的channel 是否存在
            Channel findChanel = users.find(receiverChannel.id());
            UserRedisDto userRedisPo = redisService.getOneByUserId(receiverId);
//            users.find(ChannelId)
            if (findChanel != null || userRedisPo.getState().equals(UserState.ONLINE.type)) {
                //用户在线
                sendMsg(receiverChannel, dataContent);
            } else if (userRedisPo.getState().equals(UserState.OFFLINE.type)) {
                //TODO 离线用户处理
                // 存入mongodb
                //1. 格式化数据
                Msg msgPo = new Msg(chatMsg.getContent(), chatMsg.getType(), chatMsg.getTime(), false);
                //2. 存储数据
                mongoDbService.saveMsg(receiverId, senderId, msgPo);
                MsgContentDto msgContentDto = new MsgContentDto(MsgAction.SIGNED.type, senderId, receiverId, new Msg("" + msgPo.getTime(), MsgType.RECEIVE.type, time, false), null);
                sendMsg(channel, msgContentDto);
            } else {
                //用户为禁用状态
            }
        }
    }

    private void sendMsg(Channel channel, Object msg) {
        System.out.println("发送消息给客户端");
        channel.writeAndFlush(
                new TextWebSocketFrame(
                        JSON.toJSONString(msg)
                )
        );
    }
}
